﻿using BeetleX.Clients;
using EventNext;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

#nullable disable
namespace BeetleX.XRPC.Clients
{
    public class XRPCClientDispatch : DispatchProxy, IHeader //,INetClient
    {
        private Dictionary<string, ClientActionHandler> mHandlers = new Dictionary<string, ClientActionHandler>();

        public Type Type { get; set; }

        public Dictionary<string, ClientActionHandler> Handlers => this.mHandlers;

        public XRPCClient Client { get; set; }

        public string Actor { get; set; }

        public Dictionary<string, string> Header { get; private set; } = new Dictionary<string, string>();

        public AsyncTcpClient TcpClient { get; set; }

        public string ServiceName { get; set; }

        internal void InitHandlers()
        {
            Type type = this.Type;
            string str = "/" + (type.GetCustomAttribute<ServiceAttribute>(false)?.Name ?? type.Name) + "/";
            this.ServiceName = str;
            foreach (MethodInfo method in type.GetMethods(BindingFlags.Instance | BindingFlags.Public))
            {
                if (string.Compare("Equals", method.Name, true) != 0 && string.Compare("GetHashCode", method.Name, true) != 0 && string.Compare("GetType", method.Name, true) != 0 && string.Compare("ToString", method.Name, true) != 0 && method.Name.IndexOf("set_") < 0 && method.Name.IndexOf("get_") < 0)
                {
                    ActionAttribute customAttribute = method.GetCustomAttribute<ActionAttribute>(false);
                    string actionUrl = str + (customAttribute == null ? method.Name : customAttribute.Name);
                    if (this.mHandlers.Values.FirstOrDefault<ClientActionHandler>((Func<ClientActionHandler, bool>)(c => c.Url == actionUrl)) != null)
                        throw new XRPCException(type.Name + "." + method.Name + " action already exists, can add ActionAttribute on the method");
                    this.mHandlers[method.Name] = new ClientActionHandler(method)
                    {
                        Url = actionUrl
                    };
                }
            }
        }

        protected override object Invoke(MethodInfo targetMethod, object[] args)
        {
            ClientActionHandler clientActionHandler;
            if (!this.mHandlers.TryGetValue(targetMethod.Name, out clientActionHandler))
                throw new XRPCException(targetMethod.Name + " action not found!") { ErrorCode = 404 };
            if (!clientActionHandler.IsTaskResult)
                throw new XRPCException("Definition is not supported, please define task with return value!") { ErrorCode = 403 };

            using RPCPacket request = new RPCPacket();
            request.Url = clientActionHandler.Url;
            request.Data = args;
            if (!string.IsNullOrEmpty(this.Actor))
            {
                request.Header = new Dictionary<string, string>();
                request.Header["ACTOR"] = this.Actor;
            }
            if (this.Header.Count > 0)
            {
                if (request.Header == null)
                    request.Header = new Dictionary<string, string>();
                foreach (KeyValuePair<string, string> keyValuePair in Header)
                    request.Header.Add(keyValuePair.Key, keyValuePair.Value);
            }
            Task<RPCPacket> task = this.Client.SendWait(request, this.TcpClient, clientActionHandler.ResponseType);
            if (!clientActionHandler.IsTaskResult)
            {
                if (task.Wait(this.Client.TimeOut))
                {
                    RPCPacket result = task.Result;
                    if (result.Status == (short)200)
                        return result.Paramters > 0 ? result.Data[0] : (object)null;
                    this.Client.AwaiterFactory.GetItem(request.ID);
                    throw new XRPCException((string)result.Data[0])
                    {
                        ErrorCode = result.Status
                    };
                }
                throw new XRPCException(targetMethod.Name + " action time out!")
                {
                    ErrorCode = 408
                };
            }
            IAnyCompletionSource completionSource = clientActionHandler.GetCompletionSource();
            completionSource.WaitResponse(task);
            return (object)completionSource.GetTask();
        }
    }
}
